﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using FreeDAM.Domain.Abstract;
using FreeDAM.Domain.Entities;

namespace FreeDAM.Renderer
{
    // NOTE: You can use the "Rename" command on the "Refactor" menu to change the class name "RenderService" in code, svc and config file together.
    // NOTE: In order to launch WCF Test Client for testing this service, please select RenderService.svc or RenderService.svc.cs at the Solution Explorer and start debugging.
    public class RenderService : IRenderService
    {
        private readonly IAssetToRenderRepository AssetToRenderRepository;
        private readonly IRenditionDefinitionRepository RenditionDefinitionRepository;
        private readonly IRenditionRepository RenditionRepository;
        private readonly ITagRepository TagRepository;

        public RenderService(IAssetToRenderRepository assetToRenderRepository, IRenditionDefinitionRepository renditionDefinitionRepository,
            IRenditionRepository renditionRepository, ITagRepository tagRepository)
        {
            AssetToRenderRepository = assetToRenderRepository;
            RenditionDefinitionRepository = renditionDefinitionRepository;
            RenditionRepository = renditionRepository;
            TagRepository = tagRepository;
        }

        public void CreateRenditions()
        {
            IList<AssetToRender> assetsToRender = AssetToRenderRepository.All.Where(atr => atr.Status == "Ready To Process").ToList();
            IList<RenditionDefinition> renditionDefinitions = RenditionDefinitionRepository.All.ToList();
            string extension, toFileName = null, toDirectoryLocation = null;
            Rendition currentRendition;
            bool renditionCreated;

            foreach (AssetToRender assetToRender in assetsToRender)
            {
                assetToRender.Status = "Processing";
                AssetToRenderRepository.InsertOrUpdate(assetToRender);
                AssetToRenderRepository.Save();

                extension = Path.GetExtension(assetToRender.Asset.FileName);

                // if this asset had been rendered before, delete the existing renditions
                foreach (Rendition rendition in RenditionRepository.All.Where(r => r.AssetId == assetToRender.AssetId))
                {
                    // Delete the file
                    File.Delete(rendition.DirectoryLocation);
                    // Delete the entry in the database
                    RenditionRepository.Delete(rendition.Id);
                }

                // create rendition(s)
                // need to revisit this based on Rendition Groups
                foreach (RenditionDefinition currentRenditionDefinition in assetToRender.Asset.RenditionGroup.RenditionDefinitions)
                {
                    renditionCreated = false;
                    if (assetToRender.Asset.MimeType.ToLower().Contains("video"))
                    {
                        if (currentRenditionDefinition.ToFileType.ToUpper() != Path.GetExtension(assetToRender.Asset.DirectoryLocation).ToUpper())
                        {
                            toDirectoryLocation = Path.ChangeExtension(assetToRender.Asset.DirectoryLocation, currentRenditionDefinition.ToFileType);
                            toFileName = Path.GetFileName(toDirectoryLocation);
                            EncodeVideo(assetToRender.Asset.DirectoryLocation,
                                currentRenditionDefinition.CommandLineArguments,
                                Path.ChangeExtension(assetToRender.Asset.DirectoryLocation, currentRenditionDefinition.ToFileType));

                            renditionCreated = true;
                        }
                    }
                    else if (assetToRender.Asset.MimeType.ToLower().Contains("audio"))
                    {
                        if (currentRenditionDefinition.ToFileType.ToUpper() != Path.GetExtension(assetToRender.Asset.DirectoryLocation).ToUpper())
                        {
                            toDirectoryLocation = Path.ChangeExtension(assetToRender.Asset.DirectoryLocation, currentRenditionDefinition.ToFileType);
                            toFileName = Path.GetFileName(toDirectoryLocation);
                            EncodeAudio(assetToRender.Asset.DirectoryLocation,
                                currentRenditionDefinition.CommandLineArguments,
                                Path.ChangeExtension(assetToRender.Asset.DirectoryLocation, currentRenditionDefinition.ToFileType));

                            renditionCreated = true;
                        }
                    }
                    else if (assetToRender.Asset.MimeType.ToLower().Contains("image"))
                    {
                        toFileName = Path.GetFileNameWithoutExtension(assetToRender.Asset.DirectoryLocation) + currentRenditionDefinition.AppendToFileName + currentRenditionDefinition.ToFileType;
                        toDirectoryLocation = Path.Combine(Path.GetDirectoryName(assetToRender.Asset.DirectoryLocation), toFileName);
                        ConvertImage(assetToRender.Asset.DirectoryLocation,
                            currentRenditionDefinition.CommandLineArguments,
                            toDirectoryLocation);

                        renditionCreated = true;
                    }

                    if (renditionCreated)
                    {
                        currentRendition = new Rendition()
                        {
                            AssetId = assetToRender.AssetId,
                            FileName = toFileName,
                            MimeType = Constants.MimeTypes[Path.GetExtension(toFileName)],
                            DirectoryLocation = toDirectoryLocation,
                            ServerRelativePath = "~/media/" + assetToRender.AssetId.ToString("D8") + "/Asset/" + toFileName
                        };
                        currentRendition.Tags = new List<Tag>();
                        foreach (Tag currentTag in currentRenditionDefinition.Tags)
                        {
                            currentRendition.Tags.Add(TagRepository.Find(currentTag.Id));
                        }

                        RenditionRepository.InsertOrUpdate(currentRendition);
                        RenditionRepository.Save();
                    }
                }

                assetToRender.Status = "Complete";
                AssetToRenderRepository.InsertOrUpdate(assetToRender);
                AssetToRenderRepository.Save();
            }
        }

        private void EncodeVideo(string inputFile, string encodingCommand, string outputFile)
        {
            string ffmpegPath = ConfigurationManager.AppSettings["ffmpegPath"];
            string parameters = string.Format("-i \"{0}\" {1} \"{2}\"", inputFile, encodingCommand, outputFile);

            RunProcessLargeFile(ffmpegPath, parameters);
        }

        private void EncodeAudio(string inputFile, string encodingCommand, string outputFile)
        {
            string ffmpegPath = ConfigurationManager.AppSettings["ffmpegPath"];
            string parameters = string.Format("-i \"{0}\" {1} \"{2}\"", inputFile, encodingCommand, outputFile);

            RunProcessLargeFile(ffmpegPath, parameters);
        }

        private void ConvertImage(string inputFile, string encodingCommand, string outputFile)
        {
            string imageMagick = ConfigurationManager.AppSettings["imageMagick"];
            string parameters = string.Format("\"{0}[0]\" {1} \"{2}\"", inputFile, encodingCommand, outputFile);

            RunProcessLargeFile(imageMagick, parameters);
        }


        private string RunProcessLargeFile(string commandExe, string parameters)
        {
            /* The below will be the right solution ....
             * The while loop which reads the stream is very improtant
             * for FFMPEG as .NET does not provide more memory to FFMPEG.
             * When converting large files, FFMPEG's out put stream gets filled...
             * And waits for .NET to allocate memory resources but is never done.
             * In order to utilize less memory, we are clearing the buffer periodically.
             **/
            StringBuilder output = null;
            ProcessStartInfo processStartInfo = new ProcessStartInfo(commandExe, parameters);
            processStartInfo.WorkingDirectory = Path.GetDirectoryName(commandExe);
            processStartInfo.UseShellExecute = false;
            processStartInfo.CreateNoWindow = true;
            processStartInfo.RedirectStandardError = true;
            processStartInfo.RedirectStandardOutput = true;

            using (Process process = Process.Start(processStartInfo))
            {
                using (StreamReader streamReader = process.StandardError)
                {
                    output = new StringBuilder();
                    while (!process.WaitForExit(1000))
                    {
                        output.Append(streamReader.ReadToEnd());
                    }

                    if (process.ExitCode == 0)
                    {
                        output.Append(streamReader.ReadToEnd());
                        process.Close();
                    }
                    else
                    {
                        output.Append(streamReader.ReadToEnd());
                    }
                }
            }
            return output.ToString();
        }
    }
}
